package com.taglab.chic.taglib;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>
 *   Variant of FutureTask with a result that expires after a period of time after the result completes. Expiration
 *   is indicated by throwing a CancellationException (e.g. the result is 'cancelled' after a period).
 * </p>
 *
 * @author Damien Papworth
 */
public class ExpiringFutureTask<V> extends FutureTask<V> {
  // XXX There may be a subtle race condition between calling done() and isDone(), as the done() calls don't seem to
  // be guarded on the parent FutureTask.

  private final long nanoExpiration;
  private final AtomicLong expireTime = new AtomicLong();

  /**
   * <p>
   *   Construct an ExpiringFutureTask with the specified expiration time.
   * </p>
   *
   * @param callable Task to execute
   * @param expiration Arbitrary time for the result to return before being cancelled
   * @param unit Time unit to use for expiration
   */
  public ExpiringFutureTask(Callable<V> callable, long expiration, TimeUnit unit) {
    super(callable);
    this.nanoExpiration = unit.toNanos(expiration);
  }

  @Override
  public V get() throws InterruptedException, ExecutionException {
    if (isExpired()) {
      throw new CancellationException("Expired");
    }
    return super.get();
  }

  @Override
  public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    if (isExpired()) {
      throw new CancellationException("Expired");
    }
    return super.get(timeout, unit);
  }

  protected void done() {
    expireTime.set(System.nanoTime() + nanoExpiration);
  }

  public boolean isExpired() {
    return isDone() && System.nanoTime() > expireTime.get();
  }
  
}